﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace UBCon.Core.Primitives
{
    public class CommandSourceBase : ListBoxItem, ICommandSource
    {
        #region Constructors
        #endregion

        #region Properties
        public static readonly DependencyProperty ClickModeProperty =
               System.Windows.DependencyProperty.Register(
                       "ClickMode",
                       typeof(System.Windows.Controls.ClickMode),
                       typeof(CommandSourceBase),
                       new FrameworkPropertyMetadata(System.Windows.Controls.ClickMode.Release),
                       new ValidateValueCallback(IsValidClickMode));


        /// <summary>
        ///     Indicates the click behavior for the RibbonGalleryItem.
        /// </summary>
        [Bindable(true),
        Category("Behavior")]
        public System.Windows.Controls.ClickMode ClickMode
        {
            get
            {
                return (System.Windows.Controls.ClickMode)GetValue(ClickModeProperty);
            }
            set
            {
                SetValue(ClickModeProperty, value);
            }
        }

        /// <summary>
        /// Gets a value whether button state is activated. This is a dependency property.
        /// </summary>
        [Browsable(false),
        Category("Appearance"),
        ReadOnly(true)]
        public bool IsPressed
        {
            get { return (bool)GetValue(IsPressedProperty); }
        }

        // Using a DependencyProperty as the backing store for IsPressed.  This enables animation, styling, binding, etc...
        internal static readonly System.Windows.DependencyPropertyKey IsPressedPropertyKey =
            System.Windows.DependencyProperty.RegisterReadOnly("IsPressed", typeof(bool),
            typeof(CommandSourceBase),
            new FrameworkPropertyMetadata(false));

        internal static readonly DependencyProperty IsPressedProperty = IsPressedPropertyKey.DependencyProperty;

        [Bindable(true),
        Browsable(false),
        ReadOnly(false)]
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Command.  This enables animation, styling, binding, etc...

        public static readonly DependencyProperty CommandProperty =
            DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandSourceBase),
            new FrameworkPropertyMetadata((ICommand)null, new PropertyChangedCallback(OnCommandPropertyChanged)));

        /// <summary>
        /// Gets or sets command target of button. This is a dependency property.
        /// </summary>
        [Bindable(true),
        Category("Action")]
        public IInputElement CommandTarget
        {
            get { return (IInputElement)GetValue(CommandTargetProperty); }
            set { SetValue(CommandTargetProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CommandTarget.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandTargetProperty =
            DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandSourceBase),
            new FrameworkPropertyMetadata((IInputElement)null));


        /// <summary>
        /// Gets or sets command parameter of button. This is a dependency property.
        /// </summary>
        [Bindable(true),
        Category("Action")]
        [Localizability(LocalizationCategory.NeverLocalize)]
        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CommandParameter.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CommandParameterProperty =
            DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandSourceBase),
            new FrameworkPropertyMetadata((object)null));


        /// <summary>
        /// 
        /// </summary>
        public bool IsInMainFocus
        {
            get
            {
                Visual visual = FocusManager.GetFocusScope(this) as Visual;

                return visual == null || VisualTreeHelper.GetParent(visual) == null;
            }
        }
        #endregion

        #region Depenency Property Changed Callbacks

        /// <summary>
        /// Invokes whn command property changes.
        /// </summary>
        /// <param name="o"></param>
        /// <param name="e"></param>
        private static void OnCommandPropertyChanged(System.Windows.DependencyObject o, System.Windows.DependencyPropertyChangedEventArgs e)
        {
            CommandSourceBase btn = o as CommandSourceBase;
            btn.SetValue(CommandSourceBase.CommandTargetProperty, btn);
            btn.HookUpCommand(e.OldValue as ICommand, e.NewValue as ICommand);
        }

        #endregion

        #region Overrides

        /// <summary>
        /// Invokes when mouse left button down.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {

            if (ClickMode != System.Windows.Controls.ClickMode.Hover)
            {
                e.Handled = true;
                //  this.Focus();
                if (e.ButtonState == System.Windows.Input.MouseButtonState.Pressed)
                {

                    this.CaptureMouse();
                    if (IsMouseCaptured)
                    {
                        if (e.ButtonState == System.Windows.Input.MouseButtonState.Pressed)
                        {
                            if (!IsPressed)
                            {
                                SetIsPressed(true);
                            }
                        }
                        else
                        {
                            ReleaseMouseCapture();
                        }
                    }

                    if (e.ButtonState == MouseButtonState.Pressed)
                    {
                        if (!IsPressed)
                        {
                            SetIsPressed(true);
                        }
                    }
                    else
                    {
                        ReleaseMouseCapture();
                    }
                }

                if (ClickMode == System.Windows.Controls.ClickMode.Press)
                {
                    bool exceptionThrown = true;
                    try
                    {
                        OnClick();

                        exceptionThrown = false;
                    }
                    finally
                    {
                        if (exceptionThrown)
                        {
                            SetIsPressed(false);
                            ReleaseMouseCapture();
                        }
                    }
                }
            }
            base.OnMouseLeftButtonDown(e);
        }


        /// <summary>
        /// Invokes when mouse left button up.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (this.ClickMode != System.Windows.Controls.ClickMode.Hover)
            {
                e.Handled = true;
                bool shouldClick = this.IsPressed && ClickMode == ClickMode.Release;
                if (IsMouseCaptured)
                {
                    ReleaseMouseCapture();
                }
                if (shouldClick)
                {
                    OnClick();
                }

                 SetIsPressed(false);
            }

            base.OnMouseLeftButtonUp(e);
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="sizeInfo"></param>
        protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
        {
            base.OnRenderSizeChanged(sizeInfo);
            if (IsMouseCaptured && (Mouse.PrimaryDevice.LeftButton == MouseButtonState.Pressed))
            {
                UpdateIsPressed();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostKeyboardFocus(System.Windows.Input.KeyboardFocusChangedEventArgs e)
        {
            base.OnLostKeyboardFocus(e);

            if (ClickMode == System.Windows.Controls.ClickMode.Hover)
            {
                // Ignore when in hover-click mode.
                return;
            }

            if (e.OriginalSource == this)
            {
                if (IsPressed)
                {
                    SetIsPressed(false);
                }

                if (IsMouseCaptured)
                    ReleaseMouseCapture();

            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseMove(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (ClickMode != ClickMode.Hover && (IsMouseCaptured && Mouse.PrimaryDevice.LeftButton == MouseButtonState.Pressed))
            {
                UpdateIsPressed();
                e.Handled = true;
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLostMouseCapture(System.Windows.Input.MouseEventArgs e)
        {
            base.OnLostMouseCapture(e);
            if (e.OriginalSource == this && ClickMode != ClickMode.Hover)
            {
                if (IsKeyboardFocused && !IsInMainFocus)
                {
                    Keyboard.Focus(null);
                }
                SetIsPressed(false);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseEnter(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseEnter(e);
            if (HandleIsMouseOverChanged())
            {
                e.Handled = true;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e)
        {
            base.OnMouseLeave(e);
            if (HandleIsMouseOverChanged())
            {
                e.Handled = true;
            }
        }

        /// <summary>
        /// This is the method that responds to the KeyDown event.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
        {
            base.OnKeyDown(e);
            if (ClickMode == ClickMode.Hover)
            {
                return;
            }
            if (e.Key == Key.Space)
            {
                if ((Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Alt)) != ModifierKeys.Alt)
                {
                    if ((!IsMouseCaptured) && e.OriginalSource == this)
                    {
                        SetIsPressed(true);
                        CaptureMouse();
                        if (ClickMode == System.Windows.Controls.ClickMode.Press)
                        {
                            OnClick();
                        }

                        e.Handled = true;
                    }
                }
            }
            else if (e.Key == Key.Enter && (bool)GetValue(KeyboardNavigation.AcceptsReturnProperty))
            {
                if (e.OriginalSource == this)
                {
                    SetIsPressed(false);

                    if (IsMouseCaptured)
                    {
                        ReleaseMouseCapture();
                    }

                    OnClick();
                    e.Handled = true;
                }
            }
            else
            {
                SetIsPressed(false);
                if (IsMouseCaptured)
                {
                    ReleaseMouseCapture();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="e"></param>
        protected override void OnKeyUp(System.Windows.Input.KeyEventArgs e)
        {
            base.OnKeyUp(e);

            if (ClickMode == ClickMode.Hover)
            {
                // Ignore when in hover-click mode.
                return;
            }

            if ((e.Key == Key.Space))
            {
                // Alt+Space should bring up system menu, we shouldn't handle it.
                if ((Keyboard.Modifiers & (ModifierKeys.Control | ModifierKeys.Alt)) != ModifierKeys.Alt)
                {
                    if (GetMouseLeftButtonReleased())
                    {
                        bool shouldClick = this.IsPressed && this.ClickMode == System.Windows.Controls.ClickMode.Release;

                        // Release mouse capture if left mouse button is not pressed
                        if (IsMouseCaptured)
                        {
                            // OnLostMouseCapture set IsPressed to false
                            ReleaseMouseCapture();
                        }

                        if (shouldClick)
                            OnClick();
                    }
                    else
                    {
                        // IsPressed state is updated only if mouse is captured (bugfix 919349)
                        if (IsMouseCaptured)
                            UpdateIsPressed();
                    }

                    e.Handled = true;
                }
            }
        }


        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        [SecurityCritical, SecuritySafeCritical]
        private bool GetMouseLeftButtonReleased()
        {
            return InputManager.Current.PrimaryMouseDevice.LeftButton == MouseButtonState.Released;
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual void OnClick()
        {
            RoutedEventArgs args = new RoutedEventArgs(CommandSourceBase.ClickEvent, this);

            bool exceptionThrown = true;
            try
            {
                this.RaiseEvent(args);

                if (this.Command != null)
                {
                    System.Windows.Input.RoutedCommand commad = this.Command as System.Windows.Input.RoutedCommand;

                    if (commad != null)
                    {
                        commad.Execute(this.CommandParameter, this.CommandTarget);
                    }
                    else
                    {
                        (this.Command as System.Windows.Input.ICommand).Execute(this.CommandParameter);
                    }
                }
                exceptionThrown = false;
            }
            finally
            {
                if (exceptionThrown)
                {
                    throw new System.InvalidOperationException("Cannot raise Click event.");
                }
            }

        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pressed"></param>
        private void SetIsPressed(bool pressed)
        {
            if (pressed)
            {
                SetValue(IsPressedPropertyKey, pressed);
            }
            else
            {
                ClearValue(IsPressedPropertyKey);
            }
        }


        #endregion

        #region Public Methods
        #endregion

        #region Internal Methods
        #endregion

        #region Private Methods

        /// <summary>
        /// 
        /// </summary>
        private void UpdateIsPressed()
        {
            Point pos = Mouse.PrimaryDevice.GetPosition(this);

            if ((pos.X >= 0) && (pos.X <= this.ActualWidth) && (pos.Y >= 0) && (pos.Y <= this.ActualHeight))
            {
                if (!IsPressed)
                {
                    SetIsPressed(true);
                }
            }
            else if (this.IsPressed)
            {
                SetIsPressed(false);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static bool IsValidClickMode(object value)
        {
            System.Windows.Controls.ClickMode clickMode = (System.Windows.Controls.ClickMode)value;
            return clickMode == System.Windows.Controls.ClickMode.Press
                || clickMode == System.Windows.Controls.ClickMode.Release
                || clickMode == System.Windows.Controls.ClickMode.Hover;
        }

        /// <summary>
        /// Removes old command.
        /// </summary>
        /// <param name="oldCommand"></param>
        /// <param name="newCommand"></param>
        private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
        {
            EventHandler handler = this.CanExecuteChanged;
            oldCommand.CanExecuteChanged -= handler;
        }

        /// <summary>
        /// Sets new command.
        /// </summary>
        /// <param name="oldCommand"></param>
        /// <param name="newCommand"></param>
        private void AddCommand(System.Windows.Input.ICommand oldCommand, System.Windows.Input.ICommand newCommand)
        {
            EventHandler handler = new EventHandler(CanExecuteChanged);
            canExecuteChangedHandler = handler;
            if (newCommand != null)
            {
                newCommand.CanExecuteChanged += canExecuteChangedHandler;
            }
        }

        /// <summary>
        /// Hooking up command.
        /// </summary>
        /// <param name="oldCommand"></param>
        /// <param name="newCommand"></param>
        private void HookUpCommand(ICommand oldCommand, ICommand newCommand)
        {
            if (oldCommand != null)
            {
                RemoveCommand(oldCommand, newCommand);
            }

            AddCommand(oldCommand, newCommand);
        }

        /// <summary>
        /// Checks whether can execute or not.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void CanExecuteChanged(object sender, System.EventArgs e)
        {
            if (Command != null)
            {
                RoutedCommand command = Command as RoutedCommand;
                //If Routed command
                if (command != null)
                {
                    if (command.CanExecute(CommandParameter, CommandTarget))
                    {
                        this.IsEnabled = true;
                    }
                    else
                    {
                        this.IsEnabled = false;
                    }
                }
                // If not a routed command
                else
                {
                    if (Command.CanExecute(CommandParameter))
                    {
                        IsEnabled = true;
                    }
                    else
                    {
                        IsEnabled = false;
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        private bool HandleIsMouseOverChanged()
        {
            if (ClickMode == ClickMode.Hover)
            {
                if (IsMouseOver)
                {
                    // Hovering over the button will click in the OnHover click mode
                    SetIsPressed(true);
                    OnClick();
                }
                else
                {
                    SetIsPressed(false);
                }

                return true;
            }

            return false;
        }

        #endregion

        #region Event Handlers
        #endregion

        #region Fields
        private static EventHandler canExecuteChangedHandler;
        #endregion

        #region Events

        public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent
         (
         "Click",
          RoutingStrategy.Bubble,
          typeof(RoutedEventHandler),
          typeof(CommandSourceBase)
         );

        /// <summary>
        /// 
        /// </summary>
        [Category("Behavior")]
        public event RoutedEventHandler Click
        {
            add { AddHandler(CommandSourceBase.ClickEvent, value); }
            remove { RemoveHandler(CommandSourceBase.ClickEvent, value); }
        }

        #endregion
    }
}
